Add <permissions> element validation per SPS 1.10#1133
Add <permissions> element validation per SPS 1.10#1133
Conversation
Implement 10 validation rules for the <permissions> element: 1. Presence of <permissions> in <article-meta> (CRITICAL) 2. Uniqueness of <permissions> (ERROR) 3. Presence of <license> (CRITICAL) 4. @license-type="open-access" (CRITICAL) 5. @xLink:href presence (CRITICAL) 6. @xml:lang presence (CRITICAL) 7. <license-p> presence (CRITICAL) 8. Valid CC-BY URL (ERROR) 9. Language/link consistency (ERROR) 10. Copyright structure validation (WARNING) Files added: - packtools/sps/validation/permissions.py - packtools/sps/validation_rules/permissions_rules.json - tests/sps/validation/test_permissions.py Files modified: - packtools/sps/validation/xml_validations.py (integration) - packtools/sps/validation/xml_validator.py (orchestrator) Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
Rossi-Luciano
left a comment
There was a problem hiding this comment.
Evidência de revisão — permissions.py
Data: 2026-04-23
Revisor: Luciano
Artefatos analisados:
permissions.py— módulo de validaçãopermissions_rules.json— parâmetros de configuraçãopermissions.xml— XML artificial gerado para cobertura de testespermissions-2026-23-04-124346-errors.csv— relatório de erros gerado pelo packtools
Metodologia
Análise cruzada entre três artefatos: o módulo .py, o XML artificial construído com o princípio do defeito isolado (um defeito por caso de teste), e o relatório CSV produzido pelo processamento do XML pelo packtools. Para cada caso documentado no XML verificou-se: se o módulo deveria disparar o erro (leitura das regras do código), se o CSV contém a entrada correspondente, se o nível de severidade está correto, e se casos válidos estão corretamente ausentes do CSV.
Regras validadas (10 no total)
| # | Método | Nível configurado |
|---|---|---|
| 1 | validate_permissions_presence |
CRITICAL |
| 2 | validate_permissions_uniqueness |
ERROR |
| 3 | validate_license_presence |
CRITICAL |
| 4 | validate_license_type |
CRITICAL |
| 5 | validate_xlink_href_presence |
CRITICAL |
| 6 | validate_xml_lang_presence |
CRITICAL |
| 7 | validate_license_p_presence |
CRITICAL |
| 8 | validate_license_url |
ERROR |
| 9 | validate_lang_link_consistency |
ERROR |
| 10 | validate_copyright_structure |
WARNING |
Casos de teste no XML artificial
| Caso | Regra | Defeito introduzido | Nível esperado |
|---|---|---|---|
| P1 | 1,3,4,5,6,7,8,9 | Nenhum — caso ouro completo | OK (ausente do CSV) |
| C1* | 1 | <permissions> ausente |
CRITICAL |
| C2* | 2 | <permissions> duplicado |
ERROR |
| C3 | 4 | @license-type ausente (obtained=None) |
CRITICAL |
| C4 | 4 | @license-type="closed" (valor incorreto) |
CRITICAL |
| C5 | 5 | @xlink:href ausente; regras 8 e 9 pulam (href=None) |
CRITICAL |
| C6 | 6 | @xml:lang ausente; regra 9 pula (lang=None) |
CRITICAL |
| C7 | 7 | <license-p> ausente |
CRITICAL |
| C8 | 8 | URL inválida; xml:lang="fr" impede disparo da regra 9 |
ERROR |
| C9 | 9 | lang="pt" com URL apontando para deed.en |
ERROR |
| C10 | 10 | <copyright-statement> com ano; <copyright-year> ausente |
WARNING |
* C1 e C2 requerem XMLs separados: C1 exige remoção completa do bloco <permissions>;
C2 exige dois blocos <permissions> em <article-meta>, o que introduziria ruído inevitável da regra 3 no mesmo documento.
Resultado da verificação cruzada com o CSV
Todas as 8 entradas esperadas do módulo permissions.py foram encontradas no CSV, sem falsos negativos e sem falsos positivos originados do módulo.
| Linha CSV | Caso | Nível | Verificação |
|---|---|---|---|
| 14 | C3 | CRITICAL | PASS — @license-type ausente, obtained=None |
| 15 | C4 | CRITICAL | PASS — @license-type="closed" |
| 16 | C5 | CRITICAL | PASS — @xlink:href ausente |
| 17 | C6 | CRITICAL | PASS — @xml:lang ausente |
| 18 | C7 | CRITICAL | PASS — <license-p> ausente |
| 19 | C8 | ERROR | PASS — URL example.com inválida; regra 9 não disparou (lang=fr) |
| 20 | C9 | ERROR | PASS — inconsistência lang=pt / deed.en |
| 21 | C10 | WARNING | PASS — ano "2024" no statement sem <copyright-year> |
O caso ouro P1 não aparece no CSV (is_valid=True em todas as regras).
As regras 1, 2 e 3 não geraram entradas no CSV (presença e unicidade satisfeitas;
<license> presente no bloco).
Ruído identificado (outros módulos)
Entradas no CSV não relacionadas ao permissions.py, classificadas como ruído esperado de XMLs artificiais mínimos:
- Estrutural:
abstract,historydates,fig,table-wrap,disp-formula,
inline-formula,app,subj-group[@subj-group-type="heading"]ausentes. - Ambiente local: rendição PDF não encontrada, DOI não registrado no Crossref,
issue não verificada no Core. - Metadados do elemento raiz:
specific-useausente em<article>.
Efeito colateral não antecipado: o validador de idiomas do artigo (linhas 31–32 do CSV) detectouxml:lang="en"exml:lang="fr"nas licenças de teste e exigiu títulos de artigo nessas línguas. O uso dexml:lang="fr"no caso C8 introduziu um idioma não presente no restante do documento. Mitigação para próximas criações: usarxml:lang="es"para casos de isolamento, pois é um idioma já mapeado emlang_to_deede frequentemente presente nos demais casos do XML.
Conclusão
O módulo permissions.py está correto. Todos os casos de teste dispararam os erros esperados com os níveis de severidade corretos, e o caso ouro não gerou nenhuma entrada no CSV. Não foram identificados falsos negativos, falsos positivos ou erros de nível originados do módulo.
There was a problem hiding this comment.
Pull request overview
Adds SPS 1.10 / SciELO Brasil structural validation for the <permissions> element and wires it into the existing XML validation pipeline, with a dedicated rule configuration file and a new test suite.
Changes:
- Introduces
PermissionsValidationimplementing 10 validation rules for<permissions>/<license>structure and related copyright checks. - Adds configurable rule severities and parameters via
permissions_rules.json. - Integrates the new
"permissions"group into the global validator and adds comprehensive unit tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
packtools/sps/validation/permissions.py |
New validator implementing <permissions> structural checks and emitting build_response results. |
packtools/sps/validation_rules/permissions_rules.json |
New default rule configuration (levels + URL/lang parameters) for the permissions validator. |
packtools/sps/validation/xml_validations.py |
Adds validate_permissions() entry point to instantiate and run PermissionsValidation. |
packtools/sps/validation/xml_validator.py |
Adds "permissions" as a yielded validation group in the pipeline. |
tests/sps/validation/test_permissions.py |
Adds unit tests covering valid/invalid scenarios and response structure expectations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "https://creativecommons.org/licenses/by/", | ||
| "http://creativecommons.org/licenses/by/" |
There was a problem hiding this comment.
valid_license_url_patterns is configured as a very broad prefix (.../licenses/by/), which will consider non-4.0 CC-BY URLs valid. If SciELO SPS 1.10 requires CC-BY 4.0, update these patterns to match /licenses/by/4.0/ (and keep Rule 9 for checking the deed.<lang> suffix).
| "https://creativecommons.org/licenses/by/", | |
| "http://creativecommons.org/licenses/by/" | |
| "https://creativecommons.org/licenses/by/4.0/", | |
| "http://creativecommons.org/licenses/by/4.0/" |
| statement_text = statement.text or "" | ||
| copyright_year = perm_node.find("copyright-year") | ||
|
|
||
| # Check if statement mentions a year (4 consecutive digits) | ||
| year_match = re.search(r"\b(\d{4})\b", statement_text) | ||
| if year_match and copyright_year is None: |
There was a problem hiding this comment.
In validate_copyright_structure(), statement.text only captures text before any child elements, so a year inside mixed content (e.g., with Copyright © 2025 …) may be missed and the warning won’t trigger. Use the full textual content (e.g., via "".join(statement.itertext())) when searching for the year.
| yield build_response( | ||
| title="Permissions uniqueness", | ||
| parent=self._parent, | ||
| item="article-meta", | ||
| sub_item="permissions", | ||
| validation_type="value", | ||
| is_valid=False, | ||
| expected="exactly 1 <permissions> element", | ||
| obtained=f"{count} <permissions> elements", | ||
| advice="Remove duplicate <permissions> elements. Only one <permissions> should exist in <article-meta>", | ||
| data={"permissions_count": count}, | ||
| error_level=error_level, | ||
| ) |
There was a problem hiding this comment.
In validate_permissions_uniqueness(), the validation_type is set to "value", but this check is conceptually a uniqueness constraint. Other validators in this codebase use validation_type="unique" for the same kind of rule, and keeping it consistent helps downstream consumers categorize results correctly.
| def validate_license_url(self): | ||
| """Rule 8: Validate that @xlink:href is a valid CC-BY URL.""" | ||
| error_level = self.params.get("license_url_error_level", "ERROR") | ||
| valid_patterns = self.params.get( | ||
| "valid_license_url_patterns", DEFAULT_VALID_LICENSE_URL_PATTERNS | ||
| ) | ||
| license_nodes = self._get_license_nodes() | ||
|
|
||
| for license_node in license_nodes: | ||
| href = license_node.get(XLINK_HREF) | ||
| if not href: | ||
| # Missing href is handled by validate_xlink_href_presence | ||
| continue | ||
|
|
||
| is_valid = any(href.startswith(pattern) for pattern in valid_patterns) | ||
|
|
||
| yield build_response( | ||
| title="License URL", | ||
| parent=self._parent, | ||
| item="license", | ||
| sub_item="@xlink:href", | ||
| validation_type="value", | ||
| is_valid=is_valid, | ||
| expected=f"a Creative Commons CC-BY URL starting with one of {valid_patterns}", | ||
| obtained=href, | ||
| advice=f"Use a valid Creative Commons CC-BY 4.0 URL, e.g. https://creativecommons.org/licenses/by/4.0/", | ||
| data={ | ||
| "xlink_href": href, | ||
| "lang": license_node.get(XML_LANG), | ||
| }, | ||
| error_level=error_level, | ||
| ) |
There was a problem hiding this comment.
validate_license_url() currently treats any URL starting with .../licenses/by/ as valid, which would allow CC-BY versions other than 4.0 (e.g., /by/3.0/) even though the rule/advice text targets CC-BY 4.0. Tighten this validation (and the configured patterns) to ensure the URL is for CC-BY 4.0 specifically (while still letting Rule 9 handle the deed language suffix).
O que esse PR faz?
Implementa 10 regras de validação para o elemento
<permissions>conforme SPS 1.10 e Critérios SciELO Brasil:<permissions>em<article-meta><permissions><license>@license-type="open-access"@xlink:href@xml:lang<license-p>@xml:lang↔@xlink:href(deed suffix)<copyright-year>)Onde a revisão poderia começar?
packtools/sps/validation/permissions.py— classePermissionsValidationcom todas as regras.Como este poderia ser testado manualmente?
39 testes cobrindo todos os cenários: XML válido (en/pt/es, com/sem copyright), atributos ausentes, URLs inválidas, inconsistência idioma/link, duplicidade de
<permissions>.Algum cenário de contexto que queira dar?
ArticleLicenseValidationpara validar conteúdo de licença contra dados esperados do journal. Este módulo é complementar — valida a estrutura do elemento<permissions>independente de dados do journal.permissions_rules.json.xml_validator.pycomo grupo"permissions".Arquivos criados:
packtools/sps/validation/permissions.pypacktools/sps/validation_rules/permissions_rules.jsontests/sps/validation/test_permissions.pyArquivos modificados:
packtools/sps/validation/xml_validations.py—validate_permissions()packtools/sps/validation/xml_validator.py— yield do grupo"permissions"Screenshots
N/A
Quais são tickets relevantes?
N/A
Referências
Original prompt
This section details on the original issue you should resolve
<issue_title>Criar validações para o elemento </issue_title>
<issue_description>## Objetivo
Implementar validações para o elemento
<permissions>conforme a especificação SPS 1.10 e Critérios SciELO Brasil, aumentando a conformidade de X% para 75% (9 de 12 regras).Nota: Algumas validações para
<permissions>podem já estar parcialmente implementadas no repositório. Este Issue visa reavaliar, complementar e garantir cobertura completa das regras SPS 1.10 e Critérios SciELO Brasil.Contexto
O elemento
<permissions>define condições sob as quais o conteúdo do documento pode ser usado, acessado e distribuído. Para SciELO Brasil é obrigatória a declaração de licença Creative Commons CC-BY. Validações corretas garantem conformidade com políticas de Ciência Aberta, presença de atributos obrigatórios, e consistência entre idioma e links de licença.Conformidade atual: X de 12 regras implementadas (X%)
Meta após implementação: 9 de 12 regras (75%)
Documentação SPS
Referência oficial: https://docs.google.com/document/d/1GTv4Inc2LS_AXY-ToHT3HmO66UT0VAHWJNOIqzBNSgA/edit?tab=t.0#heading=h.permissions
Regras principais conforme SPS 1.10 e Critérios SciELO Brasil:
Ocorrência:
<permissions>deve aparecer uma vez em<article-meta>Licença obrigatória (SciELO Brasil):
<license>é obrigatório<license-p>é obrigatório dentro de<license>Atributos obrigatórios em
<license>:@license-type="open-access"(obrigatório)@xlink:href(obrigatório - link CC-BY correspondente ao idioma)@xml:lang(obrigatório - idioma do texto da licença)Links válidos para
@xlink:hrefpor idioma:https://creativecommons.org/licenses/by/4.0/deed.pthttps://creativecommons.org/licenses/by/4.0/deed.enhttps://creativecommons.org/licenses/by/4.0/deed.esConsistência idioma e link:
@xml:langdeve corresponder ao idioma do link em@xlink:hrefxml:lang="pt"→ link deve terminar comdeed.ptTexto padrão para
<license-p>:Elementos de Copyright (condicionais):
<copyright-statement>- Quando PDF apresenta declaração de copyright<copyright-year>- Quando há informação de ano<copyright-holder>- Quando há informação do detentorConformidade com Critérios SciELO Brasil:
Regras a Implementar
P0 – Críticas (implementar obrigatoriamente)
<permissions><permissions>é obrigatório em<article-meta>(Critério SciELO Brasil)<permissions><permissions>deve aparecer exatamente uma vez em<article-meta><license><license>é obrigatório em<permissions>@license-type="open-access"@license-typecom valor"open-access"é obrigatório em<license>@xlink:href@xlink:hrefé obrigatório em<license>@xml:lang@xml:langé obrigatório em<license><license-p><license-p>é obrigatório em<license>@xlink:hrefdeve ser um link válido de Creative Commons CC-BY 4.0P1 – Importantes (implementar se possível)
@xml:lange@xlink:href@xml:langdeve corresponder ao idioma no link@xlink:href(pt→deed.pt, en→deed.en, es→deed.es)<copyright-statement>, validar presença de<copyright-year>quando ano estiver mencionado no statementP2 – Futuras (fora do escopo deste Issue)
<license-p>com PDF<copyright-year>Arquivos a Criar/Modificar
Avaliar existentes (podem ter validações parciais):
packtools/sps/models/permissions.pyou similar – Verificar se modelo existe💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.